/*
 * Copyright (c) 2010, Jim Hollinger
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of Jim Hollinger nor the names of its contributors
 *     may be used to endorse or promote products derived from this
 *     software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <string>
#include <sstream>

#include <pcap/pcap.h>

#ifndef WIN32
#  include <sys/socket.h>
#  include <netdb.h>
#  include <netinet/in.h>
#else
#  include <winsock.h>
#endif

#include "debughelper/debughelper.h"
#include "logger/logger.h"
#include "datachannel/rawsocketchannel.h"


using namespace datachannel;


bool RawSocketChannel::printLibVersion_flag = true;


/**
 ****************************************************************************
 * @brief  Constructor
 ****************************************************************************
**/
RawSocketChannel::RawSocketChannel(logger::Logger *logger)
    : SocketChannel(logger), pcap(NULL), bpf(NULL) {
    bpf = new uint8_t[sizeof(struct bpf_program)];
    memset(bpf, 0, sizeof(struct bpf_program));
    description[0] = '\0';
    if (printLibVersion_flag) {
        printLibVersion_flag = false;
        log->info(pcap_lib_version());
    }
}


/**
 ****************************************************************************
 * @brief  Destructor
 ****************************************************************************
**/
RawSocketChannel::~RawSocketChannel() {
    uint8_t *temp = static_cast <uint8_t *> (bpf);
    delete []temp;
    bpf = NULL;
}


/**
 ****************************************************************************
 * @brief  Opens the socket.
 *
 * @param  descriptor  IP address to open
 *                      ip_address:port[:active|:passive][:tcp|:udp]
 *                      default = passive + udp
 ****************************************************************************
**/
bool RawSocketChannel::open(const std::string &descriptor) {
    close();
    processDescriptor(descriptor);

    // construct packet filter program
    std::stringstream packet_filter_stream;
    if (type == SocketChannel::TCP) {
        packet_filter_stream << "tcp";
    } else {
        packet_filter_stream << "udp";
    }
    if (mode == SocketChannel::ACTIVE) {
        packet_filter_stream << " src port " << ip_port;
    } else {
        packet_filter_stream << " dst port " << ip_port;
    }
    std::string packet_filter(packet_filter_stream.str());

    // retrieve the interfaces list
    pcap_if_t *alldevs = NULL;
    char errbuf[PCAP_ERRBUF_SIZE + 1];
    errbuf[0] = '\0';
    if (pcap_findalldevs/*_ex*/(/*source, NULL,*/ &alldevs, errbuf) < 0) {
        log->error("RawSocketChannel: Error in pcap_findalldevs: %s", errbuf);
    } else {
        if (alldevs == NULL) {
            if (errbuf[0] == '\0') {
                log->error("RawSocketChannel: No avaliable interfaces");
            } else {
                log->error("RawSocketChannel: No avaliable interfaces: %s", errbuf);
            }
        } else {

            // scan the list looking for connected interfaces
            pcap_if_t *d = NULL;
            for (d = alldevs; d != NULL; d = d->next) {
                if_print(d);

                // ignore loopback interface
                if (!(d->flags & PCAP_IF_LOOPBACK)) {

                    // scan all interface addresses
                    for (pcap_addr_t *a = d->addresses; a != NULL; a = a->next) {
                        if (a->addr->sa_family == AF_INET) {

                            // open the interface
                            pcap = openInterface(d->name,
                                                 d->description,
                                                 packet_filter.c_str(),
                                                 a->netmask);
                            break;
                        }
                    }
                }
                if (pcap != NULL) {
                    break;
                }
            }
            if (pcap == NULL) {
                if (errbuf[0] == '\0') {
                    log->error("RawSocketChannel: No supported interfaces");
                } else {
                    log->error("RawSocketChannel: No supported interfaces: %s", errbuf);
                }
            }
        }
    }
    if (alldevs != NULL) {
        pcap_freealldevs(alldevs);
    }

    return (pcap != NULL);
}


void *RawSocketChannel::openInterface(const char *interface_name,
                                      const char *interface_description,
                                      const char *packet_filter,
                                      sockaddr   *interface_netmask) {
    char errbuf[PCAP_ERRBUF_SIZE + 1];
    errbuf[0] = '\0';
    void *rc = NULL;
    rc = pcap_open_live(interface_name,  // name of the device
                        65536,    // portion of the packet to capture - it doesn't matter in this case
                        1,        // promiscuous mode (nonzero means promiscuous)
                        0,        // read timeout in msec
                        errbuf);  // error buffer
    if (rc == NULL) {
        log->error(
            "RawSocketChannel: Unable to open interface %s: %s",
            interface_name, errbuf);
    } else {
        pcap_t *p = static_cast <pcap_t *> (rc);

        if (interface_description != NULL) {
            strncpy(description, interface_description, sizeof(description));
        } else if (interface_name != NULL) {
            strncpy(description, interface_name, sizeof(description));
        } else {
            strncpy(description, "Unidentified interface", sizeof(description));
        }
        description[sizeof(description) - 1] = '\0';

        if (errbuf[0] != '\0') {
            log->warn("RawSocketChannel Warning on %s: %s",
                description, errbuf);
        }

        // verify datalink
        int dlt = pcap_datalink(p);
        if (dlt == DLT_EN10MB) {
            log->info("RawSocketChannel opened %s on %s",
                pcap_datalink_val_to_name(dlt), description);

            // disable blocking
            if (pcap_setnonblock(p, 1, errbuf) < 0) {
                log->error(
                    "RawSocketChannel: Failed to disable blocking on %s: %s",
                    description, errbuf);
            }

            // get netmask of the adapter
            sockaddr_in *in =
                reinterpret_cast <sockaddr_in *> (interface_netmask);
            uint32_t netmask = in->sin_addr.s_addr;

            // compile packet filter program
            bpf_program *code =
                reinterpret_cast <bpf_program *> (bpf);
            if (pcap_compile(p, code, packet_filter, 1, netmask) < 0) {
                log->error(
                    "RawSocketChannel: BPF failed to compile on %s: %s",
                    description, pcap_geterr(p));
            } else {
                if (pcap_setfilter(p, code) < 0) {
                    log->error(
                        "RawSocketChannel: Failed to set BPF on %s: %s",
                        description, pcap_geterr(p));
                }
            }
        } else {
            log->error(
                "RawSocketChannel: Unsupported datalink %s on %s",
                pcap_datalink_val_to_name(dlt), description);
            pcap_close(p);
            rc = NULL;
        }
    }

    return rc;
}


/**
 ****************************************************************************
 * @brief  Closes the socket.
 ****************************************************************************
**/
bool RawSocketChannel::close() {
    bool flag = true;

    if (pcap != NULL) {
        pcap_t *p = static_cast <pcap_t *> (pcap);
        struct bpf_program *code = reinterpret_cast <struct bpf_program *> (bpf);
        pcap_freecode(code);
        pcap_close(p);
        pcap = NULL;
        log->info("RawSocketChannel closed %s", description);
    }

    return flag;
}


/**
 ****************************************************************************
 * @brief  Reads from the socket.
 ****************************************************************************
**/
int RawSocketChannel::read(void *buffer, int maxCount) {
    int numRead = 0;

    if (pcap != NULL) {

        // receive the next avaliable packet
        pcap_t *p = static_cast <pcap_t *> (pcap);
        struct pcap_pkthdr *pkt_header = NULL;
        const uint8_t       *pkt_data = NULL;
        int rc = pcap_next_ex(p, &pkt_header, &pkt_data);
        if (rc > 0) {
            numRead = processPacket(pkt_data, pkt_header->caplen, buffer, maxCount);
        } else if (rc < 0) {
            log->warn("RawSocketChannel.read: Failed to read packets on %s: %s",
                description, pcap_geterr(p));
        }
    }

    return numRead;
}


/**
 ****************************************************************************
 * @brief  Interpret the raw packet data and copy the payload to buffer.
 *
 * @return Number of bytes constituting the payload.
 ****************************************************************************
**/
int RawSocketChannel::processPacket(const void *raw_data, int raw_len, void *buffer, int maxCount) {
    int rc = 0;
    const void *pld_data = NULL;
    uint32_t pld_len = 0;
    const uint8_t *pkt_data = static_cast <const uint8_t *> (raw_data);
    const MAC_header *mac = reinterpret_cast <const MAC_header *> (pkt_data);
    uint32_t mac_len = sizeof(MAC_header);
    uint16_t ether_type = ntohs(mac->ether_type);
    if (ether_type == 0x0800) {  // IPv4

        // retireve the position of the ip header
        const IP_header *ip =
            reinterpret_cast <const IP_header *> (pkt_data + mac_len);
        uint32_t ip_len = (ip->ver_ihl & 0x0F) * 4;

        if (ip->proto == TCP) {

            // TCP
            const TCP_header *tcp =
                reinterpret_cast <const TCP_header *> (pkt_data + mac_len + ip_len);
            uint32_t tcp_len = sizeof(TCP_header);
            pld_data = reinterpret_cast <const uint8_t *> (tcp + tcp_len);
            pld_len  = raw_len - (mac_len + ip_len + tcp_len);

        } else if (ip->proto == UDP) {

            // UDP
            const UDP_header *udp =
                reinterpret_cast <const UDP_header *> (pkt_data + mac_len + ip_len);
            uint32_t udp_len = sizeof(UDP_header);
            pld_data = reinterpret_cast <const uint8_t *> (udp) + udp_len;
            pld_len  = ntohs(udp->len) - udp_len;
            uint32_t pld2_len = raw_len - (mac_len + ip_len + udp_len);
#if (0)
            uint8_t *data = (uint8_t *) pld_data;
            printf("%02X %02X %02X %02X %02X %02X %02X %02X ",
                data[0], data[1],  data[2],  data[3],  data[4],  data[5],  data[6],  data[7]);
            printf("%02X %02X %02X %02X %02X %02X %02X %02X\n",
                data[8], data[9],  data[10],  data[11],  data[12],  data[13],  data[14],  data[15]);
#endif
            if (pld_len != pld2_len) {
                log->warn("RawSocketChannel.processPacket: UDP lengths do not match: %d != %d",
                    pld_len, pld2_len);
            }
        } else {
            log->debug("RawSocketChannel.processPacket Unsupported IPv4 protocol: %d",
                ip->proto);
        }
    } else {
        log->debug("RawSocketChannel.processPacket Unsupported Ether type: 0x%04X",
            ether_type);
    }

    if (pld_len > 0) {
        if (pld_len <= static_cast <uint32_t> (maxCount)) {
            rc = pld_len;
        } else {
            rc = maxCount;
        }
        memcpy(buffer, pld_data, rc);
    }

    return rc;
}


/**
 ****************************************************************************
 * @brief  Writes to the socket.
 ****************************************************************************
**/
int RawSocketChannel::write(const void *buffer, int count) {
    int numWritten = 0;

    if (pcap != NULL) {

        // send down the packet
        pcap_t *p = static_cast <pcap_t *> (pcap);
        uint8_t *buf = const_cast <uint8_t *> (static_cast <const uint8_t *> (buffer));

        // TODO: fill in mac + ip + tcp/udp header

        int rc = pcap_sendpacket(p,         // adapter
                                 buf,       // buffer with the packet
                                 count);    // size
        if (rc >= 0) {
            numWritten = count;
        } else {
            log->warn("RawSocketChannel.write: Failed to send packet on %s: %s",
                description, pcap_geterr(p));
        }
    }

    return numWritten;
}


#define IPTOSBUFFERS    (12)
/**
 ****************************************************************************
 * @brief  Convert a numeric IPv4 address to a string.
 ****************************************************************************
**/
const char *RawSocketChannel::ip4ToString(uint32_t in) {
    static char output[IPTOSBUFFERS][3*4+3+1];
    static short which = 0;

    uint8_t *p = (uint8_t *)&in;
    which = (((which + 1) == IPTOSBUFFERS) ? 0 : which + 1);
#if defined (WIN32)
    _snprintf
#else
    snprintf
#endif
        (output[which], sizeof (output[which]),
        "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);

    return output[which];
}


/**
 ****************************************************************************
 * @brief  Convert a numeric IPv6 address to a string.
 ****************************************************************************
**/
const char *RawSocketChannel::ip6ToString(struct sockaddr *sockaddr, char *address, int addrlen) {
    socklen_t sockaddrlen;

#ifdef WIN32
    sockaddrlen = sizeof (struct sockaddr_in6);
#else
    sockaddrlen = sizeof (struct sockaddr_storage);
#endif

    if (getnameinfo(sockaddr,
        sockaddrlen,
        address,
        addrlen,
        NULL,
        0,
        NI_NUMERICHOST) != 0) address = NULL;

    return address;
}


/**
 ****************************************************************************
 * @brief  Print all the available information on the given interface.
 ****************************************************************************
**/
void RawSocketChannel::if_print(const void *pcap_interface) {
    char ip6str[128];
    const pcap_if_t *d = reinterpret_cast <const pcap_if_t *> (pcap_interface);

    // name
    if (d->name != NULL) {
        printf("%s\n", d->name);
    }

    // description
    if (d->description != NULL) {
        printf("    Description: %s\n", d->description);
    }

    // loopback address
    if (d->flags) {
        printf("    Loopback: %s\n", (d->flags & PCAP_IF_LOOPBACK) ? "yes" : "no");
    }

    // ip addresses
    for (pcap_addr_t *a = d->addresses; a != NULL; a = a->next) {
        printf("    Address Family: #%d\n", a->addr->sa_family);

        switch (a->addr->sa_family) {

        case AF_INET:
            printf("    Address Family Name: AF_INET\n");
            if (a->addr != NULL)
                printf("    Address: %s\n",
                    ip4ToString(((struct sockaddr_in *) a->addr)->sin_addr.s_addr));
            if (a->netmask != NULL)
                printf("    Netmask: %s\n",
                    ip4ToString(((struct sockaddr_in *) a->netmask)->sin_addr.s_addr));
            if (a->broadaddr != NULL)
                printf("    Broadcast Address: %s\n",
                    ip4ToString(((struct sockaddr_in *) a->broadaddr)->sin_addr.s_addr));
            if (a->dstaddr != NULL)
                printf("    Destination Address: %s\n",
                    ip4ToString(((struct sockaddr_in *) a->dstaddr)->sin_addr.s_addr));
            break;

        case AF_INET6:
            printf("    Address Family Name: AF_INET6\n");
            if (a->addr != NULL)
                printf("    Address: %s\n", ip6ToString(a->addr, ip6str, sizeof (ip6str)));
            break;

        default:
            printf("    Address Family Name: Unknown\n");
            break;
        }
    }
    printf("\n");
}
